/* signal.c : signalling system (assignment 3)
 */

#include <kernel.h>
#include <i386.h>

#define MAXUINT 4294967295u //32 1's: 0b11111111111111111111111111111111
#define HIGHORDERBIT 2147483648u //aka 0b10000000000000000000000000000000

/*Structure for placing sigreturn and sigtramp arguments on the process's stack */
typedef struct Sigret_args{
	int oldrc;
	void (*handler)(void*);
	void* ctxt;
	void* osp;
	int oldignoremask;
}Sigret_args;

/* 
* Generic signal handling code that runs in application space and calls the provided
*	app- and signal-specific handler. Does not return.
*/
extern void sigtramp(void (*handler)(void*), void* ctxt, void* osp){
	if(handler == NULL){
		kprintf("ERROR: sigtramp was passed NULL handler\n");
		return;
	}
	char out[128];
	sysputs(out);

//	sysputs("APP: Bouncing off trampoline\n");
	
	Sigret_args* args = (Sigret_args*)((int)osp-sizeof(Sigret_args));	
	
	sysputs(out);
	handler(ctxt);
	sigreturn(args->osp, args->oldrc, args->oldignoremask);
	kprintf("Error: sigtramp in signal.c returned \n");
}

/*
* Scans through process p's pending signals, removing the highest priority one and delivering it
*/
extern int signalhighest(PCB* p){
	int i=0;
	unsigned int tmpmask = 0;
	for(i=31; i>=0; i--){
		tmpmask >>= 1;
		tmpmask += HIGHORDERBIT;
		if(tmpmask & p->pending_signals & p->priority_mask){
			signal(p->pid, i);
			break;
		}
	}
	return 0;
}
/*
* Actual signal delivery. Sets up application stack for sigtramp and subsequent handler, readies
* 	process after it is prepared for switch to application space
* Also sets priority_mask to mask out lower priority signals while this is being handled
*/
extern int signal (int pid, int sig){
	PCB* p = get_pcb_from_pid(pid);
	if(p == NULL){
		kprintf("KERNEL-signal(): ERROR: attempt to signal process that does not exist\n");
		return -1;
	}
	if(sig < 0 || sig > 31){
		kprintf("KERNEL-signal(): ERROR: invalid signal\n");
		return -2;
	}
	
	unsigned int i;
	unsigned int mask = 0;
	for(i=0; i<=sig; i++){ //right-fill with ones ex.  000000111
		mask <<= 1;
		mask += 1;
	}
	mask ^= MAXUINT; //flip the bits ex. 111111000
		
	i = p->priority_mask;//variable reuse :)
	p->priority_mask = mask;
	mask = i;


	/* Clear the pending signal, we have the mask we need anyways */
	i=1;
	i <<= sig;
	p->pending_signals -= i;

//	kprintf("KERNEL-signal(): Signalling process %d signal %u pending_signals %u\n", p->pid, sig, p->pending_signals);

	int positioner = p->stack;
	
	/* Put arguments on stack */
	positioner -= sizeof(Sigret_args);
	Sigret_args* sigretargs = (Sigret_args*)(positioner);	
	sigretargs->oldrc = p->rc;
	sigretargs->oldignoremask = mask;
	sigretargs->handler = p->handler_arr[sig];
	sigretargs->ctxt = p->stack;
	sigretargs->osp = p->stack;
	
	/* Create new context for handler to use */
	positioner -= sizeof(CPU_context);
	CPU_context* newct = (CPU_context*)positioner;
	newct->edi = 0;
	newct->esi = 0;
	newct->ebp = ((int)positioner);
	newct->esp = ((int)positioner);
	newct->ebx = 0;
	newct->edx = 0;
	newct->ecx = 0;
	newct->eax = 0;
	newct->iret_eip = (int)sigtramp;
	newct->iret_cs = getCS();
	newct->eflags = 0x00003200; //enable interrupts

	/* Change PCB stack to new (handler) context */
	p->stack = (void*)positioner;

//	kprintf("KERNEL-signal(): Stack set up, returning\n");
	
	return 0;	
}
/*
*	Used to add signal sig to process with pid's pending_signals if 
*	pid and sig are valid, and if that process is accepting that type
*	of signal
*/
extern int putsignal(PCB* sender, int pid, int sig){
	PCB* receiver = get_pcb_from_pid(pid);
	if(receiver == NULL){
		kprintf("KERNEL-putsignal() ERROR: attempt to put signal on nonexistant process\n");
		return -15;
	}
	if(sig < 0 || sig > 31){
		kprintf("KERNEL-putsignal() ERROR: invalid signal\n");
		return -1;
	}
	
	int newsignal = 1;
	newsignal <<= sig;
	if(newsignal & receiver->ignored_signals){ //if nonzero it means we are accepting this signal
		if(!(newsignal & receiver->pending_signals)){ //if this signal is not already set
			receiver->pending_signals += newsignal;
		}
	}

//	kprintf("KERNEL-putsignal(): Putting signal %d on process %d\n", sig, pid);

	if(receiver->state == PROC_BLOCKED || receiver->state == PROC_BLOCKEDONRECV
		|| receiver->state == PROC_BLOCKEDONSEND){
		receiver->rc == -32;
		unblock(receiver);
		ready(receiver);
	}	
	else if(receiver->state == PROC_BLOCKEDONSIG){
		receiver->rc = 1;
		unblock(receiver);
		ready(receiver);	
	}
	else if(receiver->state == PROC_SIGHANDLE){
		if(sig & receiver->priority_mask){
			receiver->rc = 0;
			ready(receiver);
		}
		else{
			return 0;
		}	
	}
	return 0;
}


/*
*	Registers a the handler with the associated signal
*	NOTE: Caller responsible for readying process
*/
extern int register_sighandler(PCB* p, int signal, void (*handler)(void*)){
	if(signal < 0 || signal > 31){
		return -1;
	}
	if((int)handler > HOLESTART && (int)handler < HOLEEND){
		return -1;
	}
	p->handler_arr[signal]=handler;

	unsigned int mask_update = 1;
	mask_update <<= signal;

	if(handler != NULL){
		p->ignored_signals |= mask_update;
	}
	else if(p->ignored_signals & mask_update){ //if it was "1" set to "0"
		p->ignored_signals -= mask_update;	
	}
	return 0;
}
